#pragma once

#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#include <iostream>

namespace crow {

// ----------------------------------------------------------------------------
// qs_parse (modified)
// https://github.com/bartgrantham/qs_parse
// ----------------------------------------------------------------------------
/*  Similar to strncmp, but handles URL-encoding for either string  */
int qs_strncmp(const char * s, const char * qs, size_t n);

/*  Finds the beginning of each key/value pair and stores a pointer in qs_kv.
 *  Also decodes the value portion of the k/v pair *in-place*.  In a future
 *  enhancement it will also have a compile-time option of sorting qs_kv
 *  alphabetically by key.  */
int qs_parse(char * qs, char * qs_kv[], int qs_kv_size);

/*  Used by qs_parse to decode the value portion of a k/v pair  */
int qs_decode(char * qs);

/*  Looks up the value according to the key on a pre-processed query string
 *  A future enhancement will be a compile-time option to look up the key
 *  in a pre-sorted qs_kv array via a binary search.  */
//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth);

/*  Non-destructive lookup of value, based on key.  User provides the
 *  destinaton string and length.  */
char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len);

// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
#undef _qsSORTING

// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
#define CROW_QS_ISHEX(x)    ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0)
#define CROW_QS_HEX2DEC(x)  (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0)
#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1)

inline int qs_strncmp(const char * s, const char * qs, size_t n) {
    int i = 0;
    unsigned char u1, u2, unyb, lnyb;

    while (n-- > 0) {
        u1 = (unsigned char)*s++;
        u2 = (unsigned char)*qs++;

        if (!CROW_QS_ISQSCHR(u1)) {
            u1 = '\0';
        }
        if (!CROW_QS_ISQSCHR(u2)) {
            u2 = '\0';
        }

        if (u1 == '+') {
            u1 = ' ';
        }
        if (u1 == '%') // easier/safer than scanf
        {
            unyb = (unsigned char)*s++;
            lnyb = (unsigned char)*s++;
            if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb)) u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
            else u1 = '\0';
        }

        if (u2 == '+') {
            u2 = ' ';
        }
        if (u2 == '%') // easier/safer than scanf
        {
            unyb = (unsigned char)*qs++;
            lnyb = (unsigned char)*qs++;
            if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb)) u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
            else u2 = '\0';
        }

        if (u1 != u2) return u1 - u2;
        if (u1 == '\0') return 0;
        i++;
    }
    if (CROW_QS_ISQSCHR(*qs)) return -1;
    else return 0;
}

inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size) {
    int i, j;
    char * substr_ptr;

    for (i = 0; i < qs_kv_size; i++)
        qs_kv[i] = NULL;

    // find the beginning of the k/v substrings or the fragment
    substr_ptr = qs + strcspn(qs, "?#");
    if (substr_ptr[0] != '\0') substr_ptr++;
    else return 0; // no query or fragment

    i = 0;
    while (i < qs_kv_size) {
        qs_kv[i] = substr_ptr;
        j = strcspn(substr_ptr, "&");
        if (substr_ptr[j] == '\0') {
            break;
        }
        substr_ptr += j + 1;
        i++;
    }
    i++;  // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs

    // we only decode the values in place, the keys could have '='s in them
    // which will hose our ability to distinguish keys from values later
    for (j = 0; j < i; j++) {
        substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
        if (substr_ptr[0] == '&' || substr_ptr[0] == '\0')  // blank value: skip decoding
        substr_ptr[0] = '\0';
        else qs_decode(++substr_ptr);
    }

#ifdef _qsSORTING
// TODO: qsort qs_kv, using qs_strncmp() for the comparison
#endif

    return i;
}

inline int qs_decode(char * qs) {
    int i = 0, j = 0;

    while (CROW_QS_ISQSCHR(qs[j])) {
        if (qs[j] == '+') {
            qs[i] = ' ';
        }
        else if (qs[j] == '%') // easier/safer than scanf
        {
            if (!CROW_QS_ISHEX(qs[j + 1]) || !CROW_QS_ISHEX(qs[j + 2])) {
                qs[i] = '\0';
                return i;
            }
            qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j + 2]);
            j += 2;
        }
        else {
            qs[i] = qs[j];
        }
        i++;
        j++;
    }
    qs[i] = '\0';

    return i;
}

inline char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth = 0) {
    int i;
    size_t key_len, skip;

    key_len = strlen(key);

#ifdef _qsSORTING
// TODO: binary search for key in the sorted qs_kv
#else  // _qsSORTING
    for (i = 0; i < qs_kv_size; i++) {
        // we rely on the unambiguous '=' to find the value in our k/v pair
        if (qs_strncmp(key, qs_kv[i], key_len) == 0) {
            skip = strcspn(qs_kv[i], "=");
            if (qs_kv[i][skip] == '=') skip++;
            // return (zero-char value) ? ptr to trailing '\0' : ptr to value
            if (nth == 0) return qs_kv[i] + skip;
            else --nth;
        }
    }
#endif  // _qsSORTING

    return NULL;
}

inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len) {
    size_t i, key_len;
    const char * tmp;

    // find the beginning of the k/v substrings
    if ((tmp = strchr(qs, '?')) != NULL) qs = tmp + 1;

    key_len = strlen(key);
    while (qs[0] != '#' && qs[0] != '\0') {
        if (qs_strncmp(key, qs, key_len) == 0) break;
        qs += strcspn(qs, "&") + 1;
    }

    if (qs[0] == '\0') return NULL;

    qs += strcspn(qs, "=&#");
    if (qs[0] == '=') {
        qs++;
        i = strcspn(qs, "&=#");
        strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1));
        qs_decode(val);
    }
    else {
        if (val_len > 0) val[0] = '\0';
    }

    return val;
}
}
// ----------------------------------------------------------------------------

namespace crow {
class query_string {
public:
    static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;

    query_string() {

    }

    query_string(const query_string& qs) :
    url_(qs.url_) {
        for (auto p : qs.key_value_pairs_) {
            key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
        }
    }

    query_string& operator =(const query_string& qs) {
        url_ = qs.url_;
        key_value_pairs_.clear();
        for (auto p : qs.key_value_pairs_) {
            key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
        }
        return *this;
    }

    query_string& operator =(query_string&& qs) {
        key_value_pairs_ = std::move(qs.key_value_pairs_);
        char* old_data = (char*)qs.url_.c_str();
        url_ = std::move(qs.url_);
        for (auto& p : key_value_pairs_) {
            p += (char*)url_.c_str() - old_data;
        }
        return *this;
    }

    query_string(std::string url) :
    url_(std::move(url)) {
        if (url_.empty()) return;

        key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);

        int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT);
        key_value_pairs_.resize(count);
    }

    void clear() {
        key_value_pairs_.clear();
        url_.clear();
    }

    friend std::ostream& operator<<(std::ostream& os, const query_string& qs) {
        os << "[ ";
        for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i) {
            if (i) os << ", ";
            os << qs.key_value_pairs_[i];
        }
        os << " ]";
        return os;

    }

    char* get(const std::string& name) const {
        char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
        return ret;
    }

    std::vector<char*> get_list(const std::string& name) const {
        std::vector<char*> ret;
        std::string plus = name + "[]";
        char* element = nullptr;

        int count = 0;
        while (1) {
            element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
            if (!element) break;
            ret.push_back(element);
        }
        return ret;
    }

private:
    std::string url_;
    std::vector<char*> key_value_pairs_;
};

} // end namespace

